import logging
from collections import defaultdict

from clutch import exceptions as exc
from clutch import G, C
from clutch.proxy import AppLocalProxy, object_manager

log = logging.getLogger(__name__)

def register_globals():
    global engines, register_jsonify
    engines = EngineRegistry()
    GenshiEngine.register()
    JSONEngine.register()
    JinjaEngine.register()
    SafeJinjaEngine.register()

def _memoize(memo, inner, *key):
    result = memo.get(key, ())
    if result == ():
        result = memo[key] = inner(*key)
    return result

class EngineRegistry(object):

    def __init__(self):
        self._engines = {}
        self._engine_classes = {}

    def register(self, name, engine):
        self._engines[name] = engine

    def get(self, name, template):
        if name == ():
            if template.endswith('.html'):
                name = G.config.clutch.view.default_html_engine
            else:
                name = G.config.clutch.view.default_text_engine
        proxy = self._engines[name]
        return object_manager(proxy).obj

class Engine(object):
    name=None
    
    def __init__(self, config):
        self.config = config
        self._loaders = {}
        try:
            self._dfl_loader = self.loader(
                G.application.app_config.package_name,
                G.application.app_config.templates.objname)
        except AttributeError:
            self._dfl_loader = None

    def loader(self, pkg_name, *tpl_path):
        tpl_dir = '/'.join(tpl_path)
        return _memoize(self._loaders, self._loader, pkg_name, tpl_dir)

    def template(self, tpl_name):
        '''Return a template based on the default loader'''
        return self._dfl_loader.template(tpl_name)

    def template_from_string(self, str):
        raise NotImplementedError, '%s.template_from_string' % (
            self.__class__.__name__)

    def render(self, value, tpl_name=None):
        '''Render using the default loader'''
        tpl = self.template(tpl_name)
        return tpl.render(value)

    def _loader(self, pkg_name, tpl_dir):
        raise NotImplementedError, '%s._loader' % (self.__class__.__name__)

    @classmethod
    def register(klass):
        engines.register(klass.name, AppLocalProxy(
            factory=klass.factory))

    @classmethod
    def factory(klass):
        if hasattr(C, 'G'):
            config = G.config.view.get(klass.name, {})
        else:
            config = {}
        return klass(config)

class Loader(object):

    def __init__(self):
        self._templates = {}

    def template(self, tpl_name):
        '''Memoize the results of _template'''
        return _memoize(self._templates, self._template, tpl_name)

    def _template(self, tpl_name):
        raise NotImplementedError, '%s._template' % (self.__class__.__name__)

class Template(object):

    def generate(self, value):
        raise NotImplementedError, '%s.generate' % (self.__class__.__name__)

    def render(self, value):
        raise NotImplementedError, '%s.render' % (self.__class__.__name__)

# Genshi support
class GenshiEngine(Engine):
    name='genshi'

    def template_from_string(self, s):
        from genshi.template import MarkupTemplate
        return GenshiTemplate(MarkupTemplate(s))

    def _loader(self, pkg_name, tpl_dir):
        from genshi.template import TemplateLoader
        pkg_loader = TemplateLoader.package(pkg_name, tpl_dir)
        g_loader = TemplateLoader([pkg_loader],
                                  **self.config)
        return GenshiLoader(g_loader)

class GenshiLoader(Loader):

    def __init__(self, genshi_loader):
        Loader.__init__(self)
        self._loader = genshi_loader

    def _template(self, tpl_name):
        import genshi.template.loader
        try:
            g_tpl = self._loader.load(tpl_name)
        except genshi.template.loader.TemplateNotFound, tnf:
            raise exc.TemplateNotFound, tnf.args[0]
        return GenshiTemplate(g_tpl)

class GenshiTemplate(Template):

    def __init__(self, genshi_tpl):
        self._template = genshi_tpl

    def generate(self, value):
        d = dict(value)
        if hasattr(C, 'G'):
            d.update(
                C=object_manager(C).freeze(),
                G=object_manager(G).freeze())
        return self._template.generate(**d)

    def render(self, value):
        return self.generate(value).serialize()

# Jinja Support
class JinjaEngine(Engine):
    name='jinja'

    def template_from_string(self, s):
        environ = self._make_environ()
        return JinjaTemplate(environ.from_string(s))

    def _make_environ(self, loader=None):
        import jinja2
        return jinja2.Environment(loader=loader, **self.config)

    def _loader(self, pkg_name, tpl_dir):
        import jinja2
        loader = jinja2.PackageLoader(pkg_name, tpl_dir)
        environ = self._make_environ(loader)
        return JinjaLoader(environ)

class SafeJinjaEngine(JinjaEngine):
    name='safe_jinja'

    def _make_environ(self, loader):
        import jinja2.sandbox
        return jinja2.sandbox.ImmutableSandboxedEnvironment(loader=loader, **self.config)

class JinjaLoader(Loader):

    def __init__(self, environ):
        Loader.__init__(self)
        self._environ = environ

    def _template(self, tpl_name):
        j_tpl = self._environ.get_template(tpl_name)
        return JinjaTemplate(j_tpl)

class JinjaTemplate(Template):

    def __init__(self, jinja_template):
        self._template = jinja_template

    def generate(self, value):
        d = dict(value)
        if hasattr(C, 'G'):
            d.update(
                C=object_manager(C).freeze(),
                G=object_manager(G).freeze())
        return self._template.generate(**d)

    def render(self, value):
        return self.generate(value)

class JSONEngine(Engine):
    name='json'

    def __init__(self, config):
        from datetime import date, time, datetime
        self._typemap = defaultdict(list)
        self.register_jsonify(datetime, self._jsonify_datetime)
        self.register_jsonify(date, self._jsonify_date)
        self.register_jsonify(time, self._jsonify_time)

    def render(self, value, template=None):
        del value['C']
        del value['G']
        import simplejson
        return [ simplejson.dumps(value, default=self.jsonify) ]

    def register_jsonify(self, type, jsonify):
        self._typemap[type] = jsonify

    def jsonify(self, obj):
        if hasattr(obj, '__json__'):
            return obj.__json__()
        klass = obj.__class__
        if hasattr(klass, 'mro'):
            types = klass.mro()
        else:
            types = [ klass ]
        for t in types:
            jsonify = self._typemap.get(t)
            if jsonify is not None: return jsonify(obj)
        import pdb; pdb.set_trace()
        raise TypeError, "I can't jsonify type %s" % klass

    def _jsonify_datetime(self, obj):
        return obj.strftime('new Date(%Y,%m,%d,%H,%M,%S)')

    def _jsonify_date(self, obj):
        return obj.strftime('new Date(%Y,%m,%d)')

    def _jsonify_time(self, obj):
        return obj.strftime('"%H:%M:%S"')

class register_jsonify(object):

    def __init__(self, type):
        self.type = type

    def __call__(self, func):
        e = engines.get('json')
        e.register_jsonify(self.type, func)
        return func

register_globals()
